﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using Jayden.Dll.Core.IO;
using System.Threading;
using System.Net;

namespace Jayden.Dll.BitTorrent
{
	public class BitTorrentTrackerUdpClient : BitTorrentPeerProvider
	{
		private BitTorrentFileManager m_File;
		private Uri m_Uri;
		private UdpClient m_Client;
		private long m_ConnectionId = -1;
		private int m_CurrentTransactionId = -1;
		private Random m_Random = new Random();
		private bool m_Connected = false;
		private bool m_Annouced = false;
		private int m_UniqueKey = -1;

		private int m_Interval;
		private int m_Leechers;
		private int m_Seeders;

		public BitTorrentTrackerUdpClient(BitTorrentFileManager file, Uri uri)
		{
			if (uri.Scheme != "udp")
				throw new Exception("Invalid uri sheme.");
			m_File = file;
			m_Uri = uri;
		}

		public void Connect()
		{
			m_Client = new UdpClient(m_Uri.Host, m_Uri.Port);
			m_CurrentTransactionId = m_Random.Next();
			byte[] buffer = new byte[16];
			MemoryStream stream = new MemoryStream(buffer);
			BigEndianWriter writer = new BigEndianWriter(stream);
			writer.Write((long)0x41727101980);
			writer.Write((int)0);
			writer.Write((int)m_CurrentTransactionId);

			m_Client.BeginReceive(ReceiveData, null);

			do
			{
				int byteSent = m_Client.Send(stream.ToArray(), buffer.Length);
				System.Console.WriteLine("trying to connect to " + m_Uri + "...");
				Thread.Sleep(15 * 1000);
			} while (!m_Connected);
		}

		private void Announce()
		{
			m_Annouced = false;
			using (MemoryStream stream = new MemoryStream())
			{
				using (BigEndianWriter writer = new BigEndianWriter(stream))
				{
					writer.Write((long)m_ConnectionId);
					writer.Write((int)1);
					m_CurrentTransactionId = m_Random.Next();
					writer.Write((int)m_CurrentTransactionId);
					writer.InnerWriter.Write(m_File.File.InfoHash);
					writer.InnerWriter.Write(m_File.PeerId);
					writer.Write((long)m_File.Downloaded);
					writer.Write((long)m_File.Left);
					writer.Write((long)m_File.Uploaded);
					writer.Write((int)2);
					writer.Write((int)0);
					if (m_UniqueKey == -1)
						m_UniqueKey = m_Random.Next();
					writer.Write((int)m_UniqueKey);
					writer.Write((int)-1);
					writer.Write((short)6667);
					writer.Write((short)0);

					m_Client.BeginReceive(ReceiveData, null);
					do
					{
						int byteSent = m_Client.Send(stream.ToArray(), (int)stream.Position);
						System.Console.WriteLine("trying to announce ...");
						Thread.Sleep(15 * 1000);
					} while (!m_Annouced);
				}
			}
		}

		private void ReceiveData(IAsyncResult ar)
		{
			IPEndPoint ep = null;
			byte[] received = m_Client.EndReceive(ar, ref ep);
			System.Console.WriteLine(received.Length + " bytes received from " + ep + " !!");
			MemoryStream stream = new MemoryStream(received);
			BigEndianReader reader = new BigEndianReader(stream);

			int action = reader.ReadInt32();

			if (!m_Connected)
			{
				int transactionId = reader.ReadInt32();
				m_ConnectionId = reader.ReadInt64();
				if (transactionId == m_CurrentTransactionId)
				{
					m_Connected = (action == 0);

					Announce();
				}
			}
			else if (action == 1)
			{
				m_Annouced = true;
				System.Console.WriteLine("announce received...");

				int transactionId = reader.ReadInt32();
				if (m_CurrentTransactionId == transactionId)
				{
					m_Interval = reader.ReadInt32();
					m_Leechers = reader.ReadInt32();
					m_Seeders = reader.ReadInt32();

					int peersCount = (received.Length - 5 * 4) / 6;
					List<BitTorrentPeer> peers = new List<BitTorrentPeer>();
					for (int c = 0; c < peersCount; c++)
					{
						int ip = reader.InnerReader.ReadInt32();
						short port = reader.ReadInt16();
						byte[] bytes = BitConverter.GetBytes(ip);
						IPAddress ipAddress = new IPAddress(bytes);
						System.Console.WriteLine("\t - {0}:{1}", ipAddress, (ushort)port);
						BitTorrentPeer peer = new BitTorrentPeer(m_File.PeerId, m_File.File, ipAddress, port);
						peers.Add(peer);
					}
					new Thread(OnBitTorrentPeerReceived).Start(peers);
					System.Console.WriteLine("next announce in {0}.", new TimeSpan(0, 0, m_Interval));
					Thread.Sleep(m_Interval * 1000);
					Announce();
				}
			}
		}

		private void OnBitTorrentPeerReceived(object peersObj) {
			List<BitTorrentPeer> peers = peersObj as List<BitTorrentPeer>;
			if (peers.Count > 0 && BitTorrentPeerReceived != null)
				BitTorrentPeerReceived(this, peers);
		}



		public event BitTorrentPeerReceived BitTorrentPeerReceived;
	}
}
